package com.xiaoyi.common.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.xiaoyi.common.redis.RedisConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisCommands;
import redis.clients.jedis.JedisPool;

@Component
public class RedisUtil {
	@Autowired
	private JedisCluster jedisCluster;
	
	@Autowired
	private JedisPool jedisPool;
	
	@Autowired
	private RedisConfig redisConfig;
	
	/**
	 * 设置一个key的过期时间（单位：秒）
	 * 在 Redis 中，带有生存时间的 key 被称为『可挥发』(volatile)的。 
	 * @param key key值
	 * @param seconds 生命周期，多少秒后过期
	 * @return 1：设置成功  0：已经超时或key不存在 /不能设置过期时间
	 */
	public long expire(final String key, final int seconds) {
		if (key == null || key.equals("")) {
			return 0;
		}
		
		JedisCommands jedisCommands = this.getResource();
		try{
			return jedisCommands.expire(key, seconds);
		}finally{
			this.closeResource(jedisCommands);
		}
	}
	
	/**
	 * 设置一个key在某个时间点过期
	 * 
	 * @param key key值
	 * @param unixTimestamp unix时间戳，从1970-01-01 00:00:00开始到现在的秒数
	 * @return 1：设置了过期时间 0：没有设置过期时间/不能设置过期时间
	 */
	public long expireAt(String key, int unixTimestamp) {
		if (key == null || key.equals("")) {
			return 0;
		}
		
		JedisCommands jedisCommands = this.getResource();
		try{
			return jedisCommands.expireAt(key, unixTimestamp);
		}finally{
			this.closeResource(jedisCommands);
		}
	}
	
	/** 
     * 删除 
     * @param key 匹配的key 
     * @return 删除成功的条数 
     */  
    public Long delKey(final String key) {  
    	
    	JedisCommands jedisCommands = this.getResource();
    	try{
    		return jedisCommands.del(key);
    	}finally{
			this.closeResource(jedisCommands);
		}
    }  
    
    /** 
     * 批量删除 
     * @param keys 匹配的key的集合 
     * @return 删除成功的条数 
     */  
    public Long delKeys(final String[] keys) {  
    	long count=0;
    	for(String key:keys){
    		count+=this.delKey(key);
    	}
        return count;
    }  
    
    /** 
     * 自增长ID,递增1
     * 一个跨jvm的id生成器，利用了redis原子性操作的特点 
     * @param key id的key 
     * @return 返回生成的Id 
     */  
    public long incr(final String key) {  
		if (key == null || key.equals("")) {
			return 0;
		}
        
        long id=this.getResource().incr(key);
        if ((id + 75807) >= Long.MAX_VALUE) {  
            // 避免溢出，重置，getSet命令之前允许incr插队，75807就是预留的插队空间  
        	JedisCommands jedisCommands = this.getResource();
        	try{
        		jedisCommands.getSet(key, "0");
        	}finally{
    			this.closeResource(jedisCommands);
    		}
        }  
        return id;        
    } 
	/**
	 * 递增指定value
	 * 
	 * @param key
	 * @param value
	 * @return 返回递增后的值
	 */
	public long incrby(String key, long value) {
		if (key == null || key.equals("")) {
			return 0;
		}
		
		JedisCommands jedisCommands = this.getResource();
		try
		{
			return jedisCommands.incrBy(key, value);
		}finally{
			this.closeResource(jedisCommands);
		}
		
	}
    /** 
     * 递减1
     * 利用了redis原子性操作的特点 
     * @param key id的key 
     * @return 返回生成的Id 
     */  
    public long decr(final String key) {  

    	long id = 0;
    	if (key == null || key.equals("")) {
    		return 0;
    	}
    	JedisCommands jedisCommands = this.getResource();
    	try{
    		 id = jedisCommands.decr(key);
    	}finally{
			this.closeResource(jedisCommands);
		}
    	return id;        
    } 
    
	/**
	 * 递减指定value
	 * 
	 * @param key
	 * @param value
	 * @return 返回递增后的值
	 */
	public long decrby(String key, long value) {
		if (key == null || key.equals("")) {
			return 0;
		}
		
		JedisCommands jedisCommands = this.getResource();
		try{
			return jedisCommands.decrBy(key, value);
		}catch(Exception ex){
			ex.printStackTrace();
			return 0;
		}finally{
			this.closeResource(jedisCommands);
		}
	}


	/**
	 * 判断key是否存在
	 * @param key
	 * @return true:存在，false:不存在
	 */
	public Boolean exists(String key){
		if (key == null || key.equals("")) {
			return false;
		}
		
		JedisCommands jedisCommands = this.getResource();
		try{
			return jedisCommands.exists(key);
		}finally{
			this.closeResource(jedisCommands);
		}
	}
	
	
	/* ======================================Strings====================================== */  
	  
    /** 
     * 将字符串值 value 关联到 key 。 
     * 如果 key 已经持有其他值， setString 就覆写旧值，无视类型。 
     * 对于某个原本带有生存时间（TTL）的键来说， 当 setString 成功在这个键上执行时， 这个键原有的 TTL 将被清除。 
     * 时间复杂度：O(1) 
     * @param key key 
     * @param value string value 
     * @return 在设置操作成功完成时，才返回 OK 。 
     */  
    public String setString(final String key, final String value) {  
    	JedisCommands jedisCommands = this.getResource();
		try{
			return jedisCommands.set(key, value); 
		}finally{
			this.closeResource(jedisCommands);
		}
    }  
	
    /** 
     * 将值 value 关联到 key ，并将 key 的生存时间设为 expire (以秒为单位)。 
     * 如果 key 已经存在， 将覆写旧值。 
     * 类似于以下两个命令: 
     * SET key value 
     * EXPIRE key expire # 设置生存时间 
     * 不同之处是这个方法是一个原子性(atomic)操作，关联值和设置生存时间两个动作会在同一时间内完成，在 Redis 用作缓存时，非常实用。 
     * 时间复杂度：O(1) 
     * @param key key 
     * @param value string value 
     * @param expire 生命周期 
     * @return 设置成功时返回 OK 。当 expire 参数不合法时，返回一个错误。 
     */  
    public String setString(final String key, final String value, final int expire) {  
    	JedisCommands jedisCommands = this.getResource();
    	try{
    		return jedisCommands.setex(key, expire, value);
    	}finally{
			this.closeResource(jedisCommands);
		}
    }  
	
    /** 
     * 将 key 的值设为 value ，当且仅当 key 不存在。若给定的 key 已经存在，则 setStringIfNotExists 不做任何动作。 
     * 时间复杂度：O(1) 
     * @param key key 
     * @param value string value 
     * @return 设置成功，返回 1 。设置失败，返回 0 。 
     */  
    public Long setStringIfNotExists(final String key, final String value) {  
    	JedisCommands jedisCommands = this.getResource();
    	try{
    		return jedisCommands.setnx(key, value);
    	}finally{
			this.closeResource(jedisCommands);
		}
    }  
    
    /** 
     * 返回 key 所关联的字符串值。如果 key 不存在那么返回特殊值 nil 。 
     * 假如 key 储存的值不是字符串类型，返回一个错误，因为 getString 只能用于处理字符串值。 
     * 时间复杂度: O(1) 
     * @param key key 
     * @return 当 key 不存在时，返回 nil ，否则，返回 key 的值。如果 key 不是字符串类型，那么返回一个错误。 
     */  
    public String getString(final String key) {  
    	JedisCommands jedisCommands = this.getResource();
    	try{
    		return jedisCommands.get(key);  
    	}finally{
			this.closeResource(jedisCommands);
		}
    }  
    
    /** 
     * 返回 key 所关联的数字值。如果 key 不存在或非数字时返回0 。 
     * 时间复杂度: O(1) 
     * @param key key 
     * @return 当 key 不存在或非数字时，返回 0 ，否则，返回 key 的long型值。
     */  
    public long getLong(final String key) {  
    	
		JedisCommands jedisCommands = getResource();
    	try{
    		return Long.parseLong(jedisCommands.get(key));  
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }  
    
    /** 
     * 返回 key 所关联的字符串值。如果 key 不存在那么返回特殊值 nil 。 
     * 假如 key 储存的值不是字符串类型，返回一个错误，因为 getString 只能用于处理字符串值。 
     * 时间复杂度: O(1) 
     * @param key key 
     * @param defaultValue 默认值 
     * @return 当 key 不存在时，返回默认值 ，否则，返回 key 的值。如果 key 不是字符串类型，那么返回一个错误。 
     */  
	public String getString(final String key,final String defaultValue) {
		JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.get(key) == null ? defaultValue : jedisCommands.get(key);
    	}finally{
    		this.closeResource(jedisCommands);
    	}
	}
    
	/** 
     * 批量的 {@link #setString(String, String)} 
     * <br><font color=red>注意：由于需要对每个key进行计算slots，所以cluster暂时还无法使用pipeline方式</font>
     * @param keys key数组 
     * @param values values数组 
     * @return 操作状态的集合 
     */  
    public List<String> batchSetString(final String[] keys,final String[] values) {  
    	if(keys==null||values==null||keys.length!=values.length){
    		throw new IllegalArgumentException("keys或values参数不合法！");
    	}
    	
    	List<String> responseList=new ArrayList<String>();
    	
    	for(int i=0;i<keys.length;i++){
    		responseList.add(this.setString(keys[i], values[i]));
    	}
    	
        return responseList;
    }
    
    /** 
     * 批量的 {@link #getString(String)} 
     * <br><font color=red>注意：由于需要对每个key进行计算slots，所以cluster暂时还无法使用pipeline方式</font>
     * @param keys key数组 
     * @return value的集合 
     */  
    public List<String> batchGetString(final String[] keys) {  
    	List<String> responseList=new ArrayList<String>();
    	if(keys==null){
    		return responseList;
    	}    	
    	for(String key:keys){
    		responseList.add(this.getString(key));
    	}
        return responseList;
    }  
    
    /* ======================================Hashes====================================== */  
    
    /** 
     * 将哈希表 key 中的域 field 的值设为 value 。 
     * 如果 key 不存在，一个新的哈希表被创建并进行 hashSet 操作。 
     * 如果域 field 已经存在于哈希表中，旧值将被覆盖。 
     * 时间复杂度: O(1) 
     * @param key key 
     * @param field 域 
     * @param value string value 
     * @return 如果 field 是哈希表中的一个新建域，并且值设置成功，返回 1 。如果哈希表中域 field 已经存在且旧值已被新值覆盖，返回 0 。 
     */  
    public Long hashSet(final String key, final String field, final String value) { 
    	JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.hset(key, field, value);
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }  
    

    /** 
     * 返回哈希表 key 中给定域 field 的值。 
     * 时间复杂度:O(1) 
     * @param key key 
     * @param field 域 
     * @return 给定域的值。当给定域不存在或是给定 key 不存在时，返回 nil 。 
     */  
    public String hashGet(final String key, final String field) {  
    	JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.hget(key, field); 
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }  
    
    /** 
     * 同时将多个 field-value (域-值)对设置到哈希表 key 中。 
     * 时间复杂度: O(N) (N为fields的数量) 
     * @param key key 
     * @param hash field-value的map 
     * @return 如果命令执行成功，返回 OK 。当 key 不是哈希表(hash)类型时，返回一个错误。 
     */  
    public String hashMultipleSet(final String key, final Map<String, String> hash) {  
    	JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.hmset(key, hash); 
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }  
    
    /** 
     * 返回哈希表 key 中，一个或多个给定域的值。如果给定的域不存在于哈希表，那么返回一个 nil 值。 
     * 时间复杂度: O(N) (N为fields的数量) 
     * @param key key 
     * @param fields field的数组 
     * @return 一个包含多个给定域的关联值的表，表值的排列顺序和给定域参数的请求顺序一样。 
     */  
    public List<String> hashMultipleGet(final String key, final String... fields) {  
    	JedisCommands jedisCommands = getResource();
    	try{
    		List<String> list = jedisCommands.hmget(key, fields);
    		return list; 
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }  
    
    /** 
     * 返回哈希表 key 中，所有的域和值。在返回值里，紧跟每个域名(field name)之后是域的值(value)，所以返回值的长度是哈希表大小的两倍。 
     * 时间复杂度: O(N) 
     * @param key key 
     * @return 以列表形式返回哈希表的域和域的值。若 key 不存在，返回空列表。 
     */  
    public Map<String, String> hashGetAll(final String key) {  
    	JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.hgetAll(key);
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }  
    
    /* ======================================List====================================== */  
    
    /** 
     * 将一个或多个值 value 插入到列表 key 的表尾(最右边)。 
     * @param key key 
     * @param values value的数组 
     * @return 执行 listPushTail 操作后，表的长度 
     */  
    public Long listPushTail(final String key, final String... values) {  
    	JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.rpush(key, values);  
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }  
    
    /** 
     * 将一个或多个值 value 插入到列表 key 的表头 (最左边)
     * @param key key 
     * @param values value的数组
     * @return 执行 listPushHead 命令后，列表的长度。 
     */  
    public Long listPushHead(final String key, final String... values) {  
    	JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.lpush(key, values);  
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }  
    
    /** 
     * 返回list所有元素，key不存在返回空列表 
     * @param key key 
     * @return list所有元素 
     */  
    public List<String> listGetAll(final String key) {  
    	JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.lrange(key, 0, -1);
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }  
    
    /** 
     * 返回指定区间内的元素，下标从0开始，负值表示从后面计算，-1表示倒数第一个元素，key不存在返回空列表 
     * @param key key 
     * @param beginIndex 下标开始索引
     * @param endIndex 下标结束索引 
     * @return 指定区间内的元素 
     */  
    public List<String> listRange(final String key, final long beginIndex, final long endIndex) {  
    	JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.lrange(key, beginIndex, endIndex);  
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }  

    /** 
     * 移除并返回列表 key 的头元素
     * @param key key 
     * @return 返回列表 key 的头元素
     */  
    public String listPop(final String key) {  
    	JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.lpop(key);  
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }  
    
    /**
     * 
     * <br>描 述：返回列表 key 的长度。
     * <br>作 者：hy
     * <br>历 史: (版本) 作者 时间 注释
     * @param key
     * @return
     */
    public long listLen(final String key){
    	JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.llen(key);
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }
    
    
    /** 
     * 一次获得多个链表的数据 
     * @param keys key的数组 
     * @return 执行结果 
     */  
    public Map<String, List<String>> batchGetAllList(final String... keys) {  
        Map<String, List<String>> result = new HashMap<String, List<String>>();  
        for (String key : keys) {  
        	List<String> list=this.listGetAll(key);
        	result.put(key, list);
        }  
        return result;
    }  
    
    /* ======================================Sorted set================================= */  
    
    /** 
     * 将一个 member 元素及其 score 值加入到有序集 key 当中。 
     * @param key key 
     * @param score score 值可以是整数值或双精度浮点数。 
     * @param member 有序集的成员 
     * @return 被成功添加的新成员的数量，不包括那些被更新的、已经存在的成员。 
     */  
    public Long addWithSortedSet(final String key, final double score, final String member) {  
    	JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.zadd(key, score, member); 
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }  
    
  
    /** 
     * 将多个 member 元素及其 score 值加入到有序集 key 当中。 
     * @param key key 
     * @param scoreMembers score、member的pair 
     * @return 被成功添加的新成员的数量，不包括那些被更新的、已经存在的成员。 
     */  
    public Long addWithSortedSet(final String key, final Map<String,Double> scoreMembers) {  
    	JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.zadd(key, scoreMembers);   
    	}finally{
    		this.closeResource(jedisCommands);
    	}
    }  
  
    /** 
     * 返回有序集 key 中， score 值介于 max 和 min 之间(默认包括等于 max 或 min )的所有的成员。 
     * 有序集成员按 score 值递减(从大到小)的次序排列。 
     * @param key key 
     * @param max score最大值 
     * @param min score最小值 
     * @return 指定区间内，带有 score 值(可选)的有序集成员的列表 
     */  
    public Set<String> revrangeByScoreWithSortedSet(final String key, final double max, final double min) {  
    	JedisCommands jedisCommands = getResource();
    	try{
    		return jedisCommands.zrevrangeByScore(key, max, min); 
    	}finally{
    		this.closeResource(jedisCommands);
    	}
        
    }  
	
    /**
     * 
     * <br>描 述：获取资源
     * @return
     */
	 private JedisCommands getResource(){
    	if(redisConfig.isCluster()){
    		return jedisCluster;
    	}else{
    		return jedisPool.getResource();
    	}
    }
	 
  /**
     * 
     * <br>描 述：释放连接资源
     * @param jedisCommands
     */
    private void closeResource(JedisCommands resource){
		if(resource instanceof Jedis){
			((Jedis)resource).close();
		}
    }

}
